package com.coderg.gmall.product.service.impl;

import com.alibaba.fastjson.JSONObject;
import com.alibaba.nacos.common.util.UuidUtils;
import com.coderg.gmall.common.constant.RedisConst;
import com.coderg.gmall.model.product.SkuInfo;
import com.coderg.gmall.product.service.RedisTestService;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.util.Arrays;
import java.util.concurrent.TimeUnit;

@Service
@Slf4j
public class RedisTestServiceImpl implements RedisTestService {

    @Autowired
    private RedisTemplate redisTemplate;
    @Autowired
    ItemServiceImpl itemService;
    @Autowired
    RedissonClient redissonClient;

    /**
     * 查询sku详情
     *
     * @param skuId
     * @return
     */
    @Override
    public SkuInfo getSkuInfo1(Long skuId) {

        //查询redis 看是否存在
        String key = RedisConst.SKUKEY_PREFIX + skuId + RedisConst.SKUKEY_SUFFIX;
        SkuInfo skuInfo = (SkuInfo) redisTemplate.opsForValue().get(key);

        if (skuInfo != null) {
            return skuInfo;
        }
        //加锁
        String uuid = UuidUtils.generateUuid();
        Boolean lock = redisTemplate.opsForValue().setIfAbsent("sku:" + skuId + ":lock", uuid, RedisConst.SKULOCK_EXPIRE_PX2, TimeUnit.SECONDS);
        if (lock) {
            try {
                //双重判断阻止排队线程查数据库
                skuInfo = (SkuInfo) redisTemplate.opsForValue().get(key);
                if (skuInfo != null) {
                    return skuInfo;
                }
                //查mysql数据库
                skuInfo = itemService.getSkuInfo(skuId);
                if (skuInfo == null || skuInfo.getId() == null) {
                    skuInfo = new SkuInfo();
                    //保存到redis
                    redisTemplate.opsForValue().set(key, skuInfo, 300, TimeUnit.SECONDS);
                } else {
                    //保存到redis
                    redisTemplate.opsForValue().set(key, skuInfo, RedisConst.SKUKEY_TIMEOUT, TimeUnit.SECONDS);
                }

            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                //使用lua脚本删除自己的锁
                //<T> T execute(RedisScript<T> var1, List<K> var2, Object... var3);
                //DefaultRedisScript 要设置返回类型
                DefaultRedisScript<Long> redisScript = new DefaultRedisScript();
                //设置返回类型
                redisScript.setResultType(Long.class);
                redisScript.setScriptText("if redis.call('get', KEYS[1]) == ARGV[1] " +
                        "then return redis.call('del', KEYS[1]) " +
                        "else return 0 end");
                redisTemplate.execute(redisScript, Arrays.asList("lock", uuid));
            }
            return skuInfo;
        } else {
            try {
                //获取锁失败:线程睡觉1秒,再次尝试
                Thread.sleep(1000);
                return getSkuInfo1(skuId);
            } catch (Exception e) {
                e.printStackTrace();
                return null;
            }
        }

    }

    /**
     * 查询sku详情
     *
     * @param skuId
     * @return
     */
    @Override
    public SkuInfo getSkuInfo2(Long skuId) {

        //先查询redis
        String s = (String) redisTemplate.opsForValue().get(RedisConst.SKUKEY_PREFIX
                + skuId + RedisConst.SKUKEY_SUFFIX);
        if (!StringUtils.isEmpty(s)) {
            SkuInfo skuInfo = (SkuInfo) JSONObject.parseObject(s, SkuInfo.class);
            return skuInfo;
        }
        //redis没有 查mysql
        RLock lock = redissonClient.getLock(RedisConst.SKUKEY_PREFIX
                + skuId + RedisConst.SKULOCK_SUFFIX);

        try {
            if (lock.tryLock(100, 60, TimeUnit.SECONDS)) {
                try {
                    //再次查询确保只查了一次mysql
                    s = (String) redisTemplate.opsForValue().get(RedisConst.SKUKEY_PREFIX
                           + skuId + RedisConst.SKUKEY_SUFFIX);
                    if (!StringUtils.isEmpty(s)) {
                        SkuInfo skuInfo = (SkuInfo) JSONObject.parseObject(s, SkuInfo.class);
                        return skuInfo;
                    }

                    SkuInfo skuInfo = itemService.getSkuInfo(skuId);
                    if (skuInfo == null || skuInfo.getId() == null){
                        skuInfo = new SkuInfo();
                        //加入redis防止，空值击穿mysql,五分钟过期
                        redisTemplate.opsForValue().set(RedisConst.SKUKEY_PREFIX
                                + skuId + RedisConst.SKUKEY_SUFFIX,JSONObject.toJSONString(skuInfo),
                                300,TimeUnit.SECONDS);
                        return null;
                    }else {
                        //查出结果加入redis,一天过期
                        redisTemplate.opsForValue().set(RedisConst.SKUKEY_PREFIX
                                        + skuId + RedisConst.SKUKEY_SUFFIX,JSONObject.toJSONString(skuInfo),
                                24*60*60,TimeUnit.SECONDS);
                        return skuInfo;
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                    log.error("加锁失败,异常内容为：",e.getMessage());
                    return null;
                } finally {
                    lock.unlock();
                }
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return null;
    }

}
